/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.trader.sale;

import ca.trader.db.SaleTableConstraints;
import ca.trader.beans.enterprise.SaleBeanLocal;
import ca.trader.beans.enterprise.UserBeanLocal;
import ca.trader.except.InvalidHttpRequestException;
import ca.trader.except.LocationNotFoundException;
import ca.trader.except.UserNotAuthenticatedException;
import ca.trader.logger.TraderLogger;
import ca.trader.regions.Location;
import ca.trader.user.User;
import ca.trader.utils.DebugUtils;
import ca.trader.utils.ObjectFactory;
import ca.trader.utils.TraderStringUtils;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Creates a sale entity and saves it in the database.
 * The GET/POST parameters are described as follows:
 * 
 * mandatory parameters:
 *	 title: the title of the sale (maximum of 200 characters)
 *	 description: the description of the sale (maximum 3000 characters)
 *	 price:  the price of the sale  (must be greater than 0)
 *    category: the category of the sale.  this value must match the name of one of the categories in SaleCategoryEnum (e.g. "Clothing").
 *    sell_in: whether to sell in the authenticated user's home city or another city.  the value of "1" specifies that the sale is to be
 *             held in user's home city.  "2" specifies that the sale sale is to be held in another city.
 * 
 * optional parameters (mandatory in-case "sell-in" parameter value is "2").  Note that in case "sell-in" parameter is "1" and these arguments
 * are passed, the user's home city is still used.
 *    country: name of the country of the sale
 *    region:  the region of the sale
 *    city:  the city of the sale
 *    
 * The output of this servlet is plain text JSON string.
 * The JSON object will have an attribute called "success".  The value of this is true or false.  True implies
 * that the sale was successfully entered.  False implies it was not.
 * For sales that were not successfully entered, a "reason" attribute is also added to the JSON string, giving the
 * reason why the issue happened
 * 
 * @author Ahsan
 */
@WebServlet(name = "DoCreateSaleEntry", urlPatterns =
{
	 "/DoCreateSaleEntry"
})
public class DoCreateSaleEntry extends HttpServlet
{
	 private static final String _className = DoCreateSaleEntry.class.getName();
	 
	 /**
	  * GET/POST argument name for the sale title
	  */
	 private static final String PARAM_SALE_TITLE = "title";
	 
	 /**
	  * GET/POST argument name for the sale description
	  */
	 private static final String PARAM_SALE_DESC = "description";
	 
	 /**
	  * GET/POST argument name for the sale price
	  */
	 private static final String PARAM_SALE_PRICE = "price";
	 
	 /**
	  * GET/POST argument name for specifying if the sale is in user's home location or in another place
	  */
	 private static final String PARAM_SALE_SEARCH_IN = "sell_in";
	 
	 /**
	  * GET/POST argument name for specifying the name of the city if using another location
	  */
	 private static final String PARAM_SALE_CITY = "city";
	 
	 /**
	  * GET/POST argument name for specifying the name of the region if using another location
	  */
	 private static final String PARAM_SALE_REGION = "region";
	 
	 /**
	  * GET/POST argument name for specifying the name of the country if using another location
	  */
	 private static final String PARAM_SALE_COUNTRY = "country";
	 
	 /**
	  * GET/POST argument name for specifying the category of the sale.  This value must be the name of the category. 
	  */
	 private static final String PARAM_SALE_CATEGORY = "category";
	 
	 /**
	  * GET/POST argument value for "sell_in" argument to use the user's default location for creating the sale
	  */
	 private static final String SALE_SEARCH_IN_USER_CITY_VAL = "1";	 
	 
	 /**
	  * GET/POST argument value for "sell_in" argument to use a specific location for creating the sale.
	  * The country/region/city GET/POST parameters become mandatory when "sell_in" has this value.
	  */
	 private static final String SALE_SEARCH_IN_ANOTHER_CITY_VAL = "2";
	 

	 /**
	  * Reference to the UserBean EJB
	  */
	 @EJB
	 UserBeanLocal userBean;
	 
	 /**
	  * Reference to the SaleBean EJB
	  */	 
	 @EJB
	 SaleBeanLocal saleBean;
	 /**
	  * Processes requests for both HTTP
	  * <code>POST</code> methods.
	  *
	  * @param request servlet request
	  * @param response servlet response
	  * @throws ServletException if a servlet-specific error occurs
	  * @throws IOException if an I/O error occurs
	  */
	 protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	 {
		  String _methodName = "processRequest";
		  
		  // set the response type as plain text
		  response.setContentType("text/plain;charset=UTF-8");
		  		  
		  // Get the reference to the writer
		  PrintWriter out = response.getWriter();
		  		  
		  TraderLogger.logInfo(_className, _methodName, "Starting servlet: " + this.getServletName());
		  
		  /* JSONObject for output.  will have an attribute "success" and a possible attribute "reason" as defined in the class description:
		  * The JSON object will have an attribute called "success".  The value of this is true or false.  True implies
		  * that the sale was successfully entered.  False implies it was not.
		  * For sales that were not successfully entered, a "reason" attribute is also added to the JSON string, giving the
		  * reason why the issue happened
		  */
		  JSONObject jsonObject = new JSONObject();

		  // this variable marks if the operation was successful or not
		  boolean wasSuccess = false;
		  
		  // this varbiable marks whether a failure was unexplained (e.g. database not existing)
		  boolean wasUnexplainedFailure = false;

		  // Contains the reason for the failure if it happens (explained or unexplained)
		  String failureReason = "";
		  
		  // Incase the failure is unexplained, this will contain the stack trace of the failure
		  String unexplainedFailureReasonStackTrace = "";
		  
		  try
		  {  
			   TraderLogger.logInfo(_className, _methodName, "Validating POST parameters");
			   
			   // Validate the request.  This function will throw an exception if there's an issue
			   validateRequest (request);
			   
			   // Get the sale title
			   String saleTitle = request.getParameter(PARAM_SALE_TITLE).trim();
			   
			   // Get the sale description
			   String saleDesc = request.getParameter(PARAM_SALE_DESC).trim();
			   
			   // Get the sale price, store into a BigDecimal
			   BigDecimal salePrice = new BigDecimal (request.getParameter(PARAM_SALE_PRICE).trim());
			   
			   // Get the sale category by getting the name
			   SaleCategoryEnum saleCategory = SaleCategoryEnum.getSaleCategoryByName(request.getParameter(PARAM_SALE_CATEGORY).trim());
			   
			   // For the location of the sale
			   Location saleLocation;
			   
			   
			   TraderLogger.logInfo(_className, _methodName, "Fetching authenticated user from DB");
			   
			   // Get the authenticated user via the user bean
			   User user = userBean.getAuthenticatedUser(request.getSession());
			   
			   // If the "sell_in" value is SALE_SEARCH_IN_USER_CITY_VAL, set the location
			   // to the authenticated user's location
			   if (request.getParameter(PARAM_SALE_SEARCH_IN).trim().equals(SALE_SEARCH_IN_USER_CITY_VAL))
			   {
				    saleLocation = user.getLocation();
			   }// Otherwise use the location passed in the GET/POST request
			   else
			   {
				    String saleCountry = request.getParameter(PARAM_SALE_COUNTRY).trim();
				    String saleRegion = request.getParameter(PARAM_SALE_REGION).trim();
				    String saleCity = request.getParameter(PARAM_SALE_CITY).trim();
				    
				    saleLocation = ObjectFactory.createLocationFromNames(saleCountry, saleRegion, saleCity);
			   }
			   
			   // Create the sale object
			   Sale sale = ObjectFactory.createSale(user, saleCategory, saleTitle, saleDesc, salePrice, saleLocation);
			   
			   
			   TraderLogger.logInfo(_className, _methodName, String.format("Inserting the sale into the DB.  Seller ID = %d, Sale title = %s", user.getId(), TraderStringUtils.trimStringToLength(saleTitle, 30)));
			   
			   // Inser the sale into DB via the sale bean
			   saleBean.insertSale(sale);
			   
			   // set the wasSuccess flag to true (if we reach this far, it means the sale was inserted successfully)
			   wasSuccess = true;
		  
		  }
		  /**
		   *  Throw In case the request was invalid (e.g sale price is not a decimal or missing GET/POST parameter).
		   *  OR In case the location provided was invalid (only applicable when sell_in = 2)
		   */
		  catch (InvalidHttpRequestException | LocationNotFoundException ex)
		  {
			   wasSuccess = false;
			   failureReason = ex.getMessage();
		  }
		  /**
		   * If there is no authenticated user
		   */
		  catch (UserNotAuthenticatedException e)
		  {
			   wasSuccess = false;
			   failureReason = "No user is not logged in.  Please log in and try again";
		  }
		  /**
		   * Unexplained failure
		   */
		  catch (Exception e)
		  {
			   wasSuccess = false;
			   failureReason = "Unknown error: " + e.getMessage();

			   wasUnexplainedFailure = true;
			   unexplainedFailureReasonStackTrace = DebugUtils.getStackTraceAsString(e);

		  }
		  finally
		  {
			   /**
			    * If the failure was unexplained, log it
			    */
			   if (wasUnexplainedFailure)
			   {
				    TraderLogger.logSevere(_className, _methodName, failureReason, unexplainedFailureReasonStackTrace);
			   }
			   
			   try
			   {
				    // add the success attribute to the jsonObject
				    jsonObject.put("success", wasSuccess);

				    // if the request was not successful, put the reason in the jsonObject
				    if (!wasSuccess)
				    {
						jsonObject.put("reason", failureReason);
						TraderLogger.logInfo(_className, _methodName, "Unable to create sale: " + failureReason);
				    }
				    
				    // print the jsonObject
				    out.print(jsonObject.toString());
			   }
			   catch (JSONException ex)
			   {
			   }

			   out.close();


			   TraderLogger.logInfo(_className, _methodName, "Exiting servlet: " + this.getServletName());

		  }
	 }
	 
	 /**
	  * Validates the servlet request
	  * @param request the servlet request
	  * @throws InvalidHttpRequestException if there is an issue with the servlet request 
	  */
	 private static void validateRequest (HttpServletRequest request) throws InvalidHttpRequestException
	 {
		  // validate that the PARAM_SALE_TITLE and PARAM_SALE_DESC and PARAM_SALE_PRICE and PARAM_SALE_CATEGORY have been passed.
		  if (request.getParameter(PARAM_SALE_TITLE) == null || request.getParameter(PARAM_SALE_DESC) == null || request.getParameter(PARAM_SALE_PRICE) == null || request.getParameter(PARAM_SALE_CATEGORY) == null )
		  {
			   throw new InvalidHttpRequestException (String.format("One of the following parameters does not exist: %s, %s, %s, %s", PARAM_SALE_TITLE, PARAM_SALE_DESC, PARAM_SALE_PRICE,PARAM_SALE_CATEGORY));
		  }
		  
		  String saleTitle = request.getParameter(PARAM_SALE_TITLE).trim();
		  
		  // Verify that the length of the sale title is <= SaleTableConstraints.CONSTRAINT_SALE_TITLE_MAX_LENGTH
		  if (saleTitle.length() > SaleTableConstraints.CONSTRAINT_SALE_TITLE_MAX_LENGTH)
		  {			   
			   throw new InvalidHttpRequestException ("Sale title must be a maximum of " + SaleTableConstraints.CONSTRAINT_SALE_TITLE_MAX_LENGTH + " characters long.");
		  }
		  
		  
		  String saleDesc = request.getParameter(PARAM_SALE_DESC).trim();
		  		  
		  // Verify that the length of the sale description is <= SaleTableConstraints.CONSTRAINT_SALE_DESC_MAX_LENGTH
		  if (saleDesc.length() > SaleTableConstraints.CONSTRAINT_SALE_DESC_MAX_LENGTH)
		  {			   
			   throw new InvalidHttpRequestException ("Sale description must be a maximum of " + SaleTableConstraints.CONSTRAINT_SALE_DESC_MAX_LENGTH + " characters long.");
		  }
		  
		  String saleCategoryName = request.getParameter(PARAM_SALE_CATEGORY).trim();
		  
		  // Verify that the sale category name is valid
		  if (!SaleCategoryEnum.containsCategory(saleCategoryName))
		  {
			   throw new InvalidHttpRequestException (saleCategoryName + " is an illegal sale category.");
		  }
				
				
		  String salePrice = request.getParameter(PARAM_SALE_PRICE).trim();
		  
		  try
		  {
			   BigDecimal salePriceAsDecimal = new BigDecimal (salePrice);
			   
			   // verify that the sale price is > 0
			   if (salePriceAsDecimal.compareTo(new BigDecimal ("0.00")) <= 0)				    
			   {				    
				    throw new InvalidHttpRequestException ("The sale price must be greater than 0.");
			   }		   
			   
			   String salePriceIntegerPart = salePriceAsDecimal.toBigInteger().toString();
			   
			   // Verify that the integer price of the sale price is <=  SaleTableConstraints.CONSTRAINT_SALE_PRICE_MAX_INT_PART_LENGTH
			   if (salePriceIntegerPart.length() > SaleTableConstraints.CONSTRAINT_SALE_PRICE_MAX_INT_PART_LENGTH)
			   {				    
				    throw new InvalidHttpRequestException ("The sale price must be a maximum of " + SaleTableConstraints.CONSTRAINT_SALE_PRICE_MAX_INT_PART_LENGTH + " digits.");
			   }
			   
			   String salePriceAsString = salePriceAsDecimal.toPlainString();

			   
			   String salePrinceDecimalString = salePriceAsString.substring(salePriceAsString.lastIndexOf('.') + 1);
			   
			   // Verify that the decimal part of the sale price is <= SaleTableConstraints.CONSTRAINT_SALE_PRICE_MAX_DECIMAL_PART_LENGTH
			   if (salePrinceDecimalString.length() > SaleTableConstraints.CONSTRAINT_SALE_PRICE_MAX_DECIMAL_PART_LENGTH)
			   {				    
				    throw new InvalidHttpRequestException ("The sale price decimal part must be a maximum of " + SaleTableConstraints.CONSTRAINT_SALE_PRICE_MAX_DECIMAL_PART_LENGTH + " digits.");
			   }
		  } // Verify that the price is a real number
		  catch (NumberFormatException ex)
		  {
			   	throw new InvalidHttpRequestException ("The sale price is not a number.");
		  }


		  // Verify that the PARAM_SALE_SEARCH_IN has been passed
		  if (request.getParameter(PARAM_SALE_SEARCH_IN) == null)
		  {
			   	throw new InvalidHttpRequestException (String.format("The following parameters does not exist: %s", PARAM_SALE_SEARCH_IN));
		  }
		  
		  String saleSearchInVal = request.getParameter(PARAM_SALE_SEARCH_IN).trim();
		  
		  // Verify that PARAM_SALE_SEARCH_IN has a value of SALE_SEARCH_IN_USER_CITY_VAL either SALE_SEARCH_IN_ANOTHER_CITY_VAL
		  if (!saleSearchInVal.equals(SALE_SEARCH_IN_USER_CITY_VAL) && !saleSearchInVal.equals(SALE_SEARCH_IN_ANOTHER_CITY_VAL))
		  {
			   throw new InvalidHttpRequestException (String.format("%s parameter can only have these values: %s, %s", PARAM_SALE_SEARCH_IN, SALE_SEARCH_IN_USER_CITY_VAL, SALE_SEARCH_IN_ANOTHER_CITY_VAL));
		  }
		  
		  // Verify that if PARAM_SALE_SEARCH_IN is equal to SALE_SEARCH_IN_ANOTHER_CITY_VAL,
		  // then PARAM_SALE_CITY and PARAM_SALE_REGION and PARAM_SALE_COUNTRY are passed.
		  if (saleSearchInVal.equals(SALE_SEARCH_IN_ANOTHER_CITY_VAL))
		  {
			   if (request.getParameter(PARAM_SALE_CITY) == null || request.getParameter(PARAM_SALE_REGION) == null || request.getParameter(PARAM_SALE_COUNTRY) == null)
			   {
				    throw new InvalidHttpRequestException (String.format("One of the following parameters does not exist: %s, %s, %s", PARAM_SALE_CITY, PARAM_SALE_REGION, PARAM_SALE_COUNTRY));		    
			   }
		  }
	 }

	 // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
	 /**
	  * Handles the HTTP
	  * <code>POST</code> method.
	  *
	  * @param request servlet request
	  * @param response servlet response
	  * @throws ServletException if a servlet-specific error occurs
	  * @throws IOException if an I/O error occurs
	  */
	 @Override
	 protected void doPost(HttpServletRequest request, HttpServletResponse response)
		    throws ServletException, IOException
	 {
		  processRequest(request, response);
	 }
	 
	 	 // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
	 /**
	  * Handles the HTTP
	  * <code>POST</code> method.
	  *
	  * @param request servlet request
	  * @param response servlet response
	  * @throws ServletException if a servlet-specific error occurs
	  * @throws IOException if an I/O error occurs
	  */
	 @Override
	 protected void doGet(HttpServletRequest request, HttpServletResponse response)
		    throws ServletException, IOException
	 {
		  processRequest(request, response);
	 }

	 /**
	  * Returns a short description of the servlet.
	  *
	  * @return a String containing servlet description
	  */
	 @Override
	 public String getServletInfo()
	 {
		  return "Short description";
	 }// </editor-fold>

}
